﻿using System;
using System.Drawing;
using System.Windows.Forms;
using NGridDrawer.Partitions;

namespace NGridDrawer.Draw
{
    /// <LastUpdate author="Blaise Braye" Date="15/06/2008 17:07"/>
    /// <summary>
    /// This <see cref="IPartitionDrawer"/> is able to render a <see cref="DataGridView"/> using its exposed properties
    /// </summary>
    /// <remarks>
    /// The precision is better than with <c>DataGridViewNativeDrawer</c> but it is not able to
    /// draw any extended design cell styles (like a DataGridViewCellButton for instance).
    /// </remarks>
    public class DataGridViewDrawer : DataGridViewAbstractDrawer, IDisposable
    {
        /// <summary>
        /// 	<para>Initializes an instance of the <see cref="DataGridViewDrawer"/> class.</para>
        /// </summary>
        /// <remarks>
        /// Initializes the Pen which will be used to draw grid borders
        /// </remarks>
        /// <param name="gridView">Grid to draw</param>
        public DataGridViewDrawer(DataGridView gridView)
            : base(gridView)
        {
            LinePen = new Pen(GridView.GridColor, 1);
            cachedRowHeaderWidth = -1;
        }

        #region DataGridViewAbstractDrawer members

        private float[] cachedHeights { get; set; }
        private float[] cachedWidths { get; set; }
        private float cachedRowHeaderWidth { get; set; }

        /// <summary>
        /// Get the width of a column
        /// </summary>
        /// <param name="graphic">graphics used to paint</param>
        /// <param name="columnKey">index of the interesting column</param>
        /// <returns>float value, width of the interesting column</returns>
        public override float ColumnWidth(Graphics graphic, int columnKey)
        {
            if (cachedWidths == null)
                cachedWidths =
                    new float[Mapping.ColumnCount];

            if (cachedWidths[columnKey] == 0)
                cachedWidths[columnKey] =
                    DataGridViewExtension.Width(graphic, Mapping.Column(columnKey))*Scale;

            return cachedWidths[columnKey];
        }

        /// <summary>
        /// Get the width of a [row header] column
        /// </summary>
        /// <param name="graphic">graphics used to paint</param>
        /// <returns>float value, width applied the row headers</returns>
        public override float RowHeaderWidth_P(Graphics graphic)
        {
            if (cachedRowHeaderWidth<0 && Mapping.RowCount > 0)
            {
                foreach(var row in Mapping.Rows){
                    cachedRowHeaderWidth = Math.Max(cachedRowHeaderWidth,
                        DataGridViewExtension.Width(graphic, row.HeaderCell) * Scale + 1*Scale);
                }
            }

            return cachedRowHeaderWidth;
        }

        /// <summary>
        /// Get the height of a row
        /// </summary>
        /// <param name="graphic">graphics used to paint</param>
        /// <param name="rowKey">index of the interesting row</param>
        /// <returns>float value, height of the interesting row</returns>
        public override float RowHeight(Graphics graphic, int rowKey)
        {
            if (cachedHeights == null)
                cachedHeights = new float[Mapping.RowCount];

            if (cachedHeights[rowKey] == 0)
                cachedHeights[rowKey] =
                    DataGridViewExtension.RowHeight(graphic, Mapping.Row(rowKey))*Scale;
            return cachedHeights[rowKey];
        }


        /// <summary>
        /// Calculate the columns header height of the <c>DataGridView</c></summary>
        /// <remarks>
        /// Used by the <c>BuildPartitions</c> method to minimize the clip height of a partition
        /// </remarks>
        /// <param name="graphic">graphics used to render the <c>DataGridView</c></param>
        /// <returns>The height of the columns header of the <c>DataGridView</c></returns>
        protected override float GetHeaderHeight_P(Graphics graphic)
        {
            return DataGridViewExtension.HeaderHeight(graphic, GridView, Scale);
        }

        /// <summary>
        /// Compute partition bounds
        /// </summary>
        /// <param name="location">indicates proposed start position of partition</param>
        /// <param name="element">partitionElement which must be painted</param>
        /// <returns>Partition bounds</returns>
        protected override RectangleF GetPartitionBounds(PointF location, PartitionElement element)
        {
            return new RectangleF(location, element.Size);
        }


        /// <summary>Draw columns header of the <see cref="DataGridView"/>
        /// </summary>
        /// <param name="graphic">graphics used to draw</param>
        /// <param name="bounds">
        /// Bounds area where the <paramref name="element"/> can be drawn
        /// </param>
        /// <param name="element">partitionElement to paint</param>
        /// <returns>
        /// Bottom Position of the Columns Header
        /// </returns>
        protected override float DrawColumnsHeader(Graphics graphic, RectangleF bounds, PartitionElement element)
        {
            float headerheight = DataGridViewExtension.HeaderHeight(graphic, GridView, Scale);

            var headerBounds = new RectangleF(
                bounds.X + RowHeaderWidth(graphic),
                bounds.Y,
                bounds.Width - RowHeaderWidth(graphic),
                headerheight);

            graphic.FillRectangle(
                new SolidBrush(DataGridViewExtension.ColumnsHeaderBackColor(GridView)),
                headerBounds);

            var cellFormat = new StringFormat
            {
                Trimming = StringTrimming.Word,
                FormatFlags = (StringFormatFlags.LineLimit | StringFormatFlags.NoClip)
            };


            // Printing each visible cell of the header row
            int colKey = element.Column.FirstColumn;
            DataGridViewColumnHeaderCell cell = Mapping.Column(colKey).HeaderCell;

            var cellBounds =
                new RectangleF(bounds.X + RowHeaderWidth(graphic), bounds.Y, ColumnWidth(graphic, colKey), headerheight);

            var headerdrawed = new DataGridViewHeaderCell[element.Column.ColumnCount];
            headerdrawed[0] = cell;
            do
            {
                cellBounds = DrawCell(graphic, cellFormat, cell, cellBounds);


                if (++colKey <= element.Column.LastColumn)
                {
                    cell = Mapping.Column(colKey).HeaderCell;

                    cellBounds.X = cellBounds.Right;
                    cellBounds.Width = ColumnWidth(graphic, colKey);

                    headerdrawed[colKey - element.Column.FirstColumn] = cell;
                }
            } while (colKey <= element.Column.LastColumn);

            OnElementDrawn(headerdrawed, cellBounds);

            return cellBounds.Bottom; //scaledHeight;
        }


        /// <summary>
        /// Draw the containment of the <paramref name="element"/> in the specified <paramref name="graphics"/>
        /// starting at <paramref name="currentY"/> y coordinate
        /// </summary>
        /// <param name="graphics">graphics used to draw</param>
        /// <param name="clipBounds">
        /// Bounds area where the <paramref name="element"/> can be drawn
        /// </param>
        /// <param name="element">partitionElement to paint</param>
        /// <param name="currentY">
        /// Top coordinate of the <paramref name="element"/> in <paramref name="graphics"/>
        /// </param>
        /// <returns>
        /// Bottom Position of the Partition element
        /// </returns>
        protected override float DrawRows(Graphics graphics,
                                 RectangleF clipBounds,
                                 PartitionElement element, float currentY)
        {
            var bounds = new RectangleF(clipBounds.Location, element.Size);

            var cellFormat = new StringFormat
            {
                Trimming = StringTrimming.Word,
                FormatFlags = StringFormatFlags.LineLimit
            };

            var rowsdrawed = new DataGridViewRow[element.Level.RowsCount];

            for (int rowKey = element.Level.FirstRow; rowKey <= element.Level.LastRow; rowKey++)
            {
                DataGridViewRow row = Mapping.Row(rowKey);

                float rowHeight = RowHeight(graphics, rowKey);


                // Calculating the starting x coordinate that the printing process will start from
                float currentX = bounds.X;


                // Calculating the entire CurrentRow bounds                
                var rowBounds = new RectangleF(bounds.X, currentY, bounds.Width, rowHeight);
                graphics.FillRectangle(new SolidBrush(DataGridViewExtension.BackColor(row)), rowBounds);


                int colKey = element.Column.FirstColumn;



                RectangleF cellBounds = RectangleF.Empty;

                if (RowHeaderWidth(graphics) > 0)
                {


                    cellBounds =
                        new RectangleF(currentX, currentY, RowHeaderWidth(graphics), rowHeight);

                    cellBounds = DrawCell(graphics, cellFormat, Mapping.Row(rowKey).HeaderCell, cellBounds);

                    currentX = cellBounds.Right;

                }

                cellBounds =
                    new RectangleF(currentX, currentY, ColumnWidth(graphics, colKey), rowHeight);

                DataGridViewCell cell = Mapping.Cell(rowKey, colKey);

                OnElementDrawing(row, bounds);

                do
                {
                    cellFormat.Alignment = DataGridViewExtension.HorizontalAlignment(cell);
                    cellBounds = DrawCell(graphics, cellFormat, cell, cellBounds);

                    if (++colKey <= element.Column.LastColumn)
                    {
                        cell = Mapping.Cell(rowKey, colKey);
                        cellBounds.X = cellBounds.Right;
                        cellBounds.Width = ColumnWidth(graphics, colKey);
                    }
                } while (colKey <= element.Column.LastColumn);

                OnElementDrawn(row, bounds);

                currentY = cellBounds.Bottom;

                rowsdrawed[rowKey - element.Level.FirstRow] = row;
            }

            OnElementDrawn(rowsdrawed, bounds);

            return currentY;
        }


        #endregion

        /// <summary>
        /// Gets/Sets the pen used to draw grid borders
        /// </summary>
        protected Pen LinePen { get; set; }


        /// <summary>
        /// Gets/Sets the destination scale (useful to fit columns on one <see cref="PartitionColumn"/>)
        /// </summary>
        protected override float Scale
        {
            get { return base.Scale; }
            set
            {
                base.Scale = value;
                cachedWidths = null;
                cachedHeights = null;
            }
        }

        #region IDisposable Members

        /// <summary>
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        #endregion


        /// <summary>
        /// Draw a <see cref="DataGridViewCell"/> object
        /// </summary>
        /// <param name="graphics">graphics used to draw</param>
        /// <param name="cellFormat">
        /// <see cref="StringFormat"/> object to apply to the formatted value of the <paramref name="cell"/>
        /// </param>
        /// <param name="cell">
        /// this is the <see cref="DataGridViewCell"/> control to draw
        /// </param>
        /// <param name="cellBounds">
        /// bounds area of the cell to draw in <paramref name="graphics"/>
        /// </param>
        /// <returns>
        /// The bounds area used by the cell in <paramref name="graphics"/>
        /// </returns>
        protected RectangleF DrawCell(Graphics graphics, StringFormat cellFormat, DataGridViewCell cell,
                                      RectangleF cellBounds)
        {
            OnElementDrawing(cell, cellBounds);

            // Printing the cell text
            graphics.DrawString(
                cell.FormattedValue.ToString(),
                DataGridViewExtension.Font(cell, Scale),
                new SolidBrush(DataGridViewExtension.ForeColor(cell)),
                cellBounds,
                cellFormat);

            // Drawing the cell bounds
            if (GridView.RowHeadersBorderStyle != DataGridViewHeaderBorderStyle.None)
                // Draw the cell border only if the HeaderBorderStyle is not None
                graphics.DrawRectangle(LinePen, cellBounds.X, cellBounds.Y, cellBounds.Width, cellBounds.Height);

            OnElementDrawn(cell, cellBounds);

            return cellBounds;
        }

        /// <summary>
        /// </summary>
        /// <param name="disposing">
        /// </param>
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (LinePen != null)
                    LinePen.Dispose();
            }
        }
    }
}